package com.star.uc.infras.filter;

import com.alibaba.fastjson2.JSON;
import com.star.common.core.context.SpringContext;
import com.star.common.core.contract.R;
import com.star.common.core.contract.enums.ResultCodeEnum;
import com.star.common.protocol.contract.dto.HeaderDTO;
import com.star.config.api.contract.query.UserTenantConfigQuery;
import com.star.config.api.contract.vo.UserTenantConfigVO;
import com.star.config.api.service.ConfigApi;
import com.star.uc.infras.properties.SignProperties;
import com.star.common.protocol.utils.HttpParamUtils;
import com.star.common.protocol.utils.SignUtil;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.DefaultCorsProcessor;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.SortedMap;

/**
 * 验签过滤器
 *
 * @author zhouhengzhe
 * @version 1.0
 * @date 2023/9/18 11:50
 */
@Slf4j
@Component
public class SignatureFilter implements Filter {

    private static final String APPLICATION_JSON = "application/json";

    private final DefaultCorsProcessor defaultCorsProcessor = new DefaultCorsProcessor();

    private final UrlBasedCorsConfigurationSource corsConfigurationSource;

    public SignatureFilter() {
        //此处增加跨域是为了防止当验签不通过的时候，通过response响应流返回给客户端，是不会经过webConfig的，所以也需要再webConfig配置中添加跨域
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //是否允许发送Cookie信息
        corsConfiguration.setAllowCredentials(true);
        //开放哪些ip、端口、域名的访问权限，星号表示开放所有域
        corsConfiguration.addAllowedOrigin("*");
        //允许HTTP请求中的携带哪些Header信息
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.setMaxAge(18000L);
        //开放哪些Http方法，允许跨域访问
        corsConfiguration.addAllowedMethod("*");
        corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        //添加映射路径，“/**”表示对所有的路径实行全局跨域访问权限的设置
        corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
    }

    /**
     * 1、可以设置忽略数据验签，通过设置star.sign.enable=true
     * 2、如果是OPTIONS请求，直接放行
     * 3、如果是POST请求，则进行数据验签
     * 3.1、所有参数（除请求头以外）按照key排序，然后把请求头的参数按照顺序填充到map中去，接着转成json，然后md5加密，再转成大写
     * 3.2、通过公钥验签
     *
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //忽略验签，用于自己本地开发调试
        SignProperties signProperties = SpringContext.getBean(SignProperties.class);
        if (signProperties.getEnable()) {
            chain.doFilter(request, response);
            return;
        }
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        if ("OPTIONS".equalsIgnoreCase(httpServletRequest.getMethod())) {
            chain.doFilter(request, response);
            return;
        }
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        String contentType = httpServletRequest.getContentType();
        if (StringUtils.isNotBlank(contentType) && contentType.startsWith(APPLICATION_JSON)) {
            HeaderDTO headerDTO = HeaderDTO.getHeaderContext();
            if (Boolean.FALSE.equals(HeaderDTO.validateHeader(headerDTO))) {
                responseSignCheckError(httpServletRequest, httpServletResponse);
                return;
            }
            String body = IOUtils.toString(httpServletRequest.getInputStream(), StandardCharsets.UTF_8);
            log.info("加密之前的前端传过来的请求体:{}", body);
            UserTenantConfigVO userTenantConfig = SpringContext.getBean(ConfigApi.class).getUserTenantConfig(UserTenantConfigQuery.builder().tenantId(headerDTO.getTenantId()).build());
            //获取请求体，并且把他封装成一个排序map，key排序=》获取排序map，并且填充headerDTO中的参数
            SortedMap<String, String> sortedMap = HeaderDTO.fillSortMapFromHeader(
                    HttpParamUtils.joinToSortMap(body), headerDTO);
            //把排序map先转成Md5，再转成大写
            String jsonData = DigestUtils.md5Hex(JSON.toJSONString(sortedMap)).toUpperCase();
            //公钥
            String clientPublicKey = userTenantConfig.getClientPublicKey();
            //验签
            if (!SignUtil.verify(jsonData, clientPublicKey, headerDTO.getSign())) {
                responseSignCheckError(httpServletRequest, httpServletResponse);
                return;
            }
            chain.doFilter(httpServletRequest, httpServletResponse);
        } else {
            chain.doFilter(httpServletRequest, httpServletResponse);
        }
    }

    private void responseSignCheckError(HttpServletRequest httpServletRequest, HttpServletResponse servletResponse) throws IOException {
        if (CorsUtils.isCorsRequest(httpServletRequest)) {
            CorsConfiguration corsConfiguration = this.corsConfigurationSource.getCorsConfiguration(httpServletRequest);
            defaultCorsProcessor.processRequest(corsConfiguration, httpServletRequest, servletResponse);
        }
        servletResponse.setCharacterEncoding("UTF-8");
        servletResponse.setContentType("application/json; charset=utf-8");
        PrintWriter out = servletResponse.getWriter();
        R result = new R(ResultCodeEnum.ARGUMENT_NULL.getCode(), ResultCodeEnum.ARGUMENT_NULL.getDesc());
        out.append(JSON.toJSONString(result));
        out.flush();
    }

}
